package com.example.friend.service.user.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.example.common.core.constants.CacheConstants;
import com.example.common.core.constants.Constants;
import com.example.common.core.constants.HttpConstants;
import com.example.common.core.constants.TTLConstants;
import com.example.common.core.domain.LoginUser;
import com.example.common.core.domain.Result;
import com.example.common.core.domain.vo.LoginUserVO;
import com.example.common.core.enums.Identity;
import com.example.common.core.enums.ResultCode;
import com.example.common.core.enums.UserStatus;
import com.example.common.core.exception.ServiceException;
import com.example.common.security.service.TokenService;
import com.example.common.security.utils.ThreadLocalUtil;
import com.example.friend.domain.user.User;
import com.example.friend.domain.user.dto.UserDTO;
import com.example.friend.domain.user.dto.UserUpdateDTO;
import com.example.friend.domain.user.vo.UserVO;
import com.example.friend.manger.UserCacheManager;
import com.example.friend.mapper.user.UserMapper;
import com.example.friend.service.user.IUserService;
import com.example.message.service.AliSmsService;
import com.example.redis.service.RedisService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Slf4j
@Service
public class UserServiceImpl implements IUserService {
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private AliSmsService aliSmsService;
    @Autowired
    private RedisService redisService;
    @Autowired
    private TokenService tokenService;
    @Autowired
    private UserCacheManager userCacheManager;

    @Value("${sms.code-expiration:5}")
    private long phoneCodeExpiration;
            @Value("${sms.send-limit:3}")
    private Integer sendLimit;
    @Value("${sms.isSend}")
    private boolean isSend;

    @Value("${jwt.secret}")
    private String secret;

    @Value("${file.oss.downloadUrl}")
    private String downloadUrl;

    //发送验证码
    @Override
    public boolean sendCode(UserDTO userDTO) {
        //验证手机号是否合法
        if (!checkPhone(userDTO.getPhone())) {
            throw new ServiceException(ResultCode.FAILED_USER_PHONE);
        }
        //生成redis存储验证码的key
        String phoneCodeKey = getPhoneCodeKey(userDTO.getPhone());

        //检验用户有没有在短时间内多次获取验证码
        checkFequence(phoneCodeKey);

        //生成redis存储 用户每天获取验证码的次数 的key
        String codeTimeKey = getCodeTimeKey(userDTO.getPhone());
        //根据key 获取 用户每天获取验证码的次数
        Long sendTimes = redisService.getCacheObject(codeTimeKey,Long.class);
        //判断是否超出了次数
        checkSendCodeCount(codeTimeKey,sendTimes);

        //生成验证码
        String code = isSend?RandomStringUtils.randomNumeric(6): Constants.DEFAULT_CODE;
        //存储验证码到redis并设置过期时间，
        redisService.setCacheObject(phoneCodeKey, code, phoneCodeExpiration, TimeUnit.MINUTES);
        //发送验证码给对应的手机号
        if (isSend){
            relayCode(userDTO.getPhone(), code);
        }
        //验证码获取次数+1,如果key不存在，则自动创建一个
        redisService.increment(codeTimeKey);
        return true;
    }

    //根据验证码登陆
    @Override
    public String codeLogin(UserDTO userDTO) {
        //比对验证码是否正确
        checkCode(userDTO);
        //判断当前用户是新用户还是老用户
        User user = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getPhone,userDTO.getPhone()));
        if (user == null) {
            //如果为空为新用户
            //先执行注册逻辑
            User newUser = new User();
            newUser.setPhone(userDTO.getPhone());
            newUser.setStatus(UserStatus.NorMal.getValue());
            userMapper.insert(newUser);
            user =newUser;
        }
        //执行登陆逻辑
        return tokenService.createToken(secret, Identity.ORDINARY.getValue(),user.getUserId(),user.getNickName(),user.getHeadImage());
    }

    //退出登陆
    public boolean logout(String token) {
        if (StrUtil.isNotEmpty(token) &&
                token.startsWith(HttpConstants.PREFIX)) {
            token = token.replaceFirst(HttpConstants.PREFIX, StrUtil.EMPTY);
        }
        return tokenService.deleteLoginUser(token,secret);
    }

    //获取当前用户信息
    public Result<LoginUserVO> info(String token) {
        if (StrUtil.isNotEmpty(token) &&
                token.startsWith(HttpConstants.PREFIX)) {
            token = token.replaceFirst(HttpConstants.PREFIX, StrUtil.EMPTY);
        }
        LoginUser loginUser = tokenService.getLoginUser(token,secret);
        if (loginUser == null){
            return Result.fail();
        }
        log.error(loginUser.toString());
        LoginUserVO loginUserVO = new LoginUserVO();
        loginUserVO.setNickName(loginUser.getNickName());
        String headImage = loginUser.getHeadImage();
        if (StrUtil.isNotEmpty(headImage)) {
            loginUserVO.setHeadImage(downloadUrl+headImage);
        }
        return Result.ok(loginUserVO);
    }

    //获取用户详情


    @Override
    public UserVO detail() {
        //从TTL中获取用户id
        Long userId = ThreadLocalUtil.get(TTLConstants.USER_ID,Long.class);
        if(userId == null){
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        //根据userId从redis中查询对应的用户信息
        UserVO userVO = userCacheManager.getUserById(userId);
        if (userVO == null) {
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        String headImage = userVO.getHeadImage();
        if (StrUtil.isNotEmpty(headImage)) {
            userVO.setHeadImage(downloadUrl+headImage);
        }
        return userVO;
    }

    //修改个人信息

    @Override
    public Integer edit(UserUpdateDTO userUpdateDTO) {
        Long userId = ThreadLocalUtil.get(TTLConstants.USER_ID, Long.class);
        if (userId == null) {
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        User user = userMapper.selectById(userId);
        if (user == null) {
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        user.setNickName(userUpdateDTO.getNickName());
        user.setSex(userUpdateDTO.getSex());
        user.setSchoolName(userUpdateDTO.getSchoolName());
        user.setMajorName(userUpdateDTO.getMajorName());
        user.setPhone(userUpdateDTO.getPhone());
        user.setEmail(userUpdateDTO.getEmail());
        user.setWechat(userUpdateDTO.getWechat());
        user.setIntroduce(userUpdateDTO.getIntroduce());
        //更新用户缓存
        userCacheManager.refreshUser(user);
        tokenService.refreshLoginUser(user.getNickName(),user.getHeadImage(),
                ThreadLocalUtil.get(TTLConstants.USER_KEY, String.class));
        return userMapper.updateById(user);
    }

    //用户上传头像

    @Override
    public Integer updateHeadImage(String headImage) {
        Long userId = ThreadLocalUtil.get(TTLConstants.USER_ID, Long.class);
        if (userId == null) {
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        User user = userMapper.selectById(userId);
        if (user == null) {
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        user.setHeadImage(headImage);
        //更新用户缓存
        userCacheManager.refreshUser(user);
        tokenService.refreshLoginUser(user.getNickName(),user.getHeadImage(),
                ThreadLocalUtil.get(TTLConstants.USER_KEY, String.class));
        return userMapper.updateById(user);
    }

    //比对验证码是否正确
    private void checkCode(UserDTO userDTO) {
        //先从redis中查询验证码
        String phoneCodeKey = getPhoneCodeKey(userDTO.getPhone());
        String code = redisService.getCacheObject(phoneCodeKey,String.class);
        //判断验证码是否已经过期了
        if (StrUtil.isEmpty(code)){
            //如果过期了，则抛出异常
            throw new ServiceException(ResultCode.FAILED_INVALID_CODE);
        }
        //开始比较验证码是否相等
        if (!code.equals(userDTO.getCode())){
            //如果不相等，抛出异常
            throw new ServiceException(ResultCode.FAILED_ERROR_CODE);
        }
        //如果相等，则登陆成功
        //先把验证码删除
        redisService.deleteObject(phoneCodeKey);
    }

    //生成redis存储 用户每天获取验证码的次数 的key，格式为 c:t:手机号
    private String getCodeTimeKey(String phone) {
        return CacheConstants.CODE_TIME_KEY+phone;
    }

    //生成redis存储验证码的key，格式为 p:t:用户的手机号
    private String getPhoneCodeKey(String phone) {
        return CacheConstants.PHONE_TOKEN_KEY + phone;
    }

    //防止用户短时间内多次获取验证码啊
    public void checkFequence(String phoneCodeKey){
        Long expire = redisService.getExpire(phoneCodeKey,TimeUnit.SECONDS);
        if (expire != null && (phoneCodeExpiration*60-expire) < 60) {
            //抛出异常
            throw new ServiceException(ResultCode.FAILED_FREQUENT);
        }
    }
    //判断用户每天获取的验证码次数是否超出了限制
    public void checkSendCodeCount(String codeTimeKey,Long sendTimes){
        if (sendTimes != null && sendTimes >= sendLimit) {
            //超过了次数限制，则抛出异常
            throw new ServiceException(ResultCode.FAILED_TIME_LIMIT);
        }
        //如果为空，说明是当天第一次发送请求，此时就要设置/刷新获取验证码的次数，并设置过期时间
        if (sendTimes == null){
            //计算当前时间到当天24:00的时间，24:00准时过期，这样就会重新刷新次数
            long seconds = ChronoUnit.SECONDS.between(LocalDateTime.now(),
                    LocalDateTime.now().plusDays(1).withHour(0).withMinute(0).withSecond(0).withNano(0));
            //在redis，创建计数器，设置过期时间
            redisService.setCacheObject(codeTimeKey,0,seconds,TimeUnit.SECONDS);
        }

    }

    //发送验证给对应的手机号
    public void relayCode(String phone, String code){
        //发送验证码
        boolean sendMobileCode = aliSmsService.sendMobileCode(phone, code);
        //如果发送验证码失败
        if (!sendMobileCode) {
            throw new ServiceException(ResultCode.FAILED_SEND_CODE);
        }
    }

    //验证手机号是否合法
    public static boolean checkPhone(String phone) {
        Pattern regex = Pattern.compile("^1[2|3|4|5|6|7|8|9][0-9]\\d{8}$");
        Matcher matcher = regex.matcher(phone);
        return matcher.matches();
    }
}
